package il.ac.bgu.cs.heuristics.core;

import il.ac.bgu.cs.heuristics.graph.types.IActionGraph;
import il.ac.bgu.cs.heuristics.graph.types.IPath;
import il.ac.bgu.cs.heuristics.graph.types.IState;
import il.ac.bgu.cs.heuristics.graph.types.ISubstitution;
import il.ac.bgu.cs.heuristics.utils.CollectionUtils;

import java.util.Calendar;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.log4j.Logger;
import org.jgraph.graph.DefaultEdge;
import org.jgrapht.Graphs;
import org.jgrapht.alg.util.VertexDegreeComparator;
import org.jgrapht.graph.SimpleGraph;

public abstract class heuristicsEngine implements IHeuristicsEngine {

	protected IActionGraph			aGraph;
	protected final static Logger	LOG						= Logger
																	.getLogger(HeuristicEngineComplex.class);
	protected static final boolean	verbose					= true;

	protected Calendar				cal						= null;

	protected double				pathLengthSum			= 0;
	protected double				pathLengthCounter		= 0;
	protected double				clSizeSum				= 0;
	protected double				clSizeCounter			= 0;
	protected double				branchingFactor			= 0;
	protected double				branchingFactorCounter	= 0;
	protected double				returnedLevel			= 0;
	protected double				returnedLevelCounter	= 0;
	protected double				callDuration			= 0;
	protected double				callDurationCounter		= 0;
	protected double				maxCallDuration			= 0;
	long							markedTime				= 0;

	protected heuristicsEngine(IActionGraph aGraph) {

		this.aGraph = aGraph;
	}

	protected static void log(String s) {
		if (verbose)
			LOG.debug(s);
	}

	protected void markCallStart() {
		cal = Calendar.getInstance();
		this.markedTime = cal.getTimeInMillis();
	}

	protected void markCallEnd() {
		cal = Calendar.getInstance();
		long d = cal.getTimeInMillis();
		long ThisCallDuration = cal.getTimeInMillis() - this.markedTime;
		callDurationCounter++;
		callDuration += ThisCallDuration;
		maxCallDuration = Math.max(maxCallDuration, ThisCallDuration);
	}

	public String getCallDuration() {
		return "\nAvg: " + this.callDuration / this.callDurationCounter
				+ " Max: " + this.maxCallDuration;

	}

	protected void logReturnedLevel(int num) {
		returnedLevelCounter++;
		this.returnedLevel += num;
	}

	public String getAverageReturnedLevel() {
		return "\nAverageReturnLevel: " + this.returnedLevel
				/ this.returnedLevelCounter;

	}

	protected void logBranchingFactor(int num) {
		branchingFactorCounter++;
		this.branchingFactor += num;
	}

	public String getAverageBranchingFactor() {
		return "\nAverageBranchingFactor: " + this.branchingFactor
				/ this.branchingFactorCounter;

	}

	protected void logPathLength(int num) {
		pathLengthCounter++;
		this.pathLengthSum += num;
	}

	public String getAveragePathLength() {
		return "\nAveragPathLength: " + this.pathLengthSum
				/ this.pathLengthCounter;

	}

	protected void logClSize(int num) {
		clSizeCounter++;
		this.clSizeSum += num;
	}

	public String getAveragClSize() {
		return "\nAveragClSize: " + this.clSizeSum / this.clSizeCounter;

	}

	public String getStatisticString() {
		return this.getAverageBranchingFactor() + this.getAveragePathLength()
				+ this.getAveragClSize() + this.getAverageReturnedLevel()
				+ getCallDuration();
	}

	@Override
	public abstract int getHValue(IState init, IState goal);

	protected static Set<IState> getMaxIS(SimpleGraph<IState, DefaultEdge> graph) {

		Set<IState> maxIS = CollectionUtils.newLinkedHashSet();
		VertexDegreeComparator<IState, DefaultEdge> comperator = new VertexDegreeComparator<IState, DefaultEdge>(
				graph);
		TreeSet<IState> sortedTree = new TreeSet<IState>(comperator);
		sortedTree.addAll(graph.vertexSet());
		IState lowDegree = null;
		while (!sortedTree.isEmpty()) {
			lowDegree = sortedTree.first();
			maxIS.add(lowDegree);
			sortedTree.remove(lowDegree);
			sortedTree.removeAll(Graphs.neighborListOf(graph, lowDegree));
		}
		return maxIS;
	}

	protected static SimpleGraph<IState, DefaultEdge> getConstrainGraph(
			Map<IState, IPath> consistentGoalsAndPath) {
		SimpleGraph<IState, DefaultEdge> constrainGraph = new SimpleGraph<IState, DefaultEdge>(
				DefaultEdge.class);
		IState[] array = consistentGoalsAndPath.keySet().toArray(new IState[0]);
		// IState[] array = createExtendedGoalArray(consistentGoalsAndPath);
		constrainGraph.addVertex(array[0]);
		for (int i = 0; i < array.length; i++) {
			for (int j = i + 1; j < array.length; j++) {
				constrainGraph.addVertex(array[j]);
				if (array[i].isContradicting(array[j])) {
					constrainGraph.addEdge(array[i], array[j]);

				}

			}

		}
		return constrainGraph;
	}

	/**
	 * @param init
	 * @param goalsInAG
	 *            - set of goals vetexes in the action graph
	 * @param goalsPath
	 *            - initilaize in the method the set of Ipath
	 * @return the set of extended goals vertexs consistent with the init state
	 *         and the matching path . e.g consistent = the shortest path
	 *         between init and that goal given_list (constrain list) is
	 *         consistent with init.
	 * 
	 *         extended = the goal state itself + the path to it from init where
	 *         i merge the proposition of the path.
	 */
	protected Map<IState, IPath> getConsistentGoals(IState init,
			Set<IState> goalsInAG) {
		Map<IState, IPath> consistentGoalsAndPath = CollectionUtils
				.newHashMap();
		// here instead of using the goal for the path i will use the extended
		// goal.
		for (IState state : goalsInAG) {
			IPath path = aGraph.getShortestPath(init, state);
			Set<ISubstitution> given_list = path.getConstraints();
			if (init.isSatisfying(given_list)) {
				consistentGoalsAndPath.put(path.getExtendedGoalState(), path);
			}
		}
		return consistentGoalsAndPath;
	}

	protected static Set<IState> filterGoals(IState init, IState goal,
			Set<IState> goals, IGoalsFilter... filters) {
		Set<IState> filteredGoals = goals;
		for (IGoalsFilter filter : filters) {
			filteredGoals = filter.filter(init, goal, filteredGoals);
		}
		return filteredGoals;
	}

	protected interface IGoalsFilter {
		// this method return the set of goals after the filter
		Set<IState> filter(IState init, IState goal, Set<IState> goals);

	}

	protected static class AlreadyInInitFilter implements IGoalsFilter {

		@Override
		public Set<IState> filter(IState init, IState goal, Set<IState> goals) {
			Set<IState> newGoalsStates = CollectionUtils.newLinkedHashSet();
			for (IState state : goals) {
				if (!init.contains(state)) {
					newGoalsStates.add(state);
				}
			}
			return newGoalsStates;
		}
	}

	protected static class NotUsefulGoalsStateFilter implements IGoalsFilter {

		@Override
		public Set<IState> filter(IState init, IState goal, Set<IState> goals) {
			Set<IState> newGoalsStates = CollectionUtils.newLinkedHashSet();
			for (IState state : goals) {
				// UsefulSub - the sub is in the goal but is not in init
				// state.-> something we want to achieve
				int numberOfUsefulSubs = 0;
				for (ISubstitution subs : state.getSubstitutions()) {
					if (goal.contains(subs) && !init.contains(subs))
						numberOfUsefulSubs++;
				}

				// if their are at least one useful subs we want to try to
				// achieve this goals state.
				if (numberOfUsefulSubs > 0)
					newGoalsStates.add(state);
			}
			return newGoalsStates;
		}
	}
}
